#import "PlSInjector.h"
#import "PlSFlavor.h"
#import "PlSSIMBLManager.h"
#import "PlSInputManagerManager.h"
#import "PlSAppSpecifier.h"
#import "PlSPlugIn.h"
#import "PlSInjector_PermissionRepair.h"
#import "PlSInjector_RemovingFromLaunchd.h"
#import "PlSInjector_VersionCheck.h"
#import "PlSCFPreferences.h"

#import "NSAlert+L0Alert.h"

@implementation PlSInjector

- (IBAction) forceQuit:(id) sender {
	exit(1);
}

- (void) begone {
	// it's not enough to quit -- we must also remove ourselves from launchd
	// cleanly, so that both the prefpane and launchd on next boot handle us
	// properly.
	
	[self disableWithUserInteraction];
	[prefs notifyPreferencesChanged];
	[NSApp terminate:self];
}

- (NSError*) application:(NSApplication*) app willPresentError:(NSError*) err {
	L0Log(@"an error occurred = %@", err);
	return err;
}

- (void) applicationDidFinishLaunching:(NSNotification*) n {
	NSError* err = nil;
	
	if ([self requiresAuthorizationWithError:&err]) {
		[NSApp activateIgnoringOtherApps:YES];
		
		NSAlert* alert = [NSAlert alertNamed:@"PlSNeedsAuthorization"];
		[[alert window] setLevel:NSFloatingWindowLevel];
		if ([alert runModal] == NSAlertSecondButtonReturn)
			[NSApp terminate:self];
		
		err = nil; BOOL restart = NO;
		[self authorizeAndReturnError:&err requiresRestart:&restart];
		
		if (err) {
			if (!([[err domain] isEqual:NSOSStatusErrorDomain] && [err code] == errAuthorizationCanceled))
				[NSApp presentError:err];
			[self begone];
		}
		
		if (restart)
			exit(1); // launchd will pick it up and restart us		
	} else if (err) {
		[NSApp presentError:err];
		[self begone];
	}
	
	prefs = [PlSCFPreferences new];
	_updateUI = [PlSUpdateUI new];
	
	_remoteMarker = [[NSNumber numberWithBool:YES] retain];
	NSConnection* localConn = [NSConnection defaultConnection];
	[localConn setRootObject:_remoteMarker];
	if (![localConn registerName:kPlSDistributedObjectName])
		[self begone];
	
	NSWorkspace* ws = [NSWorkspace sharedWorkspace];
	[[ws notificationCenter]
	 addObserver:self
	 selector:@selector(_appLaunched:)
	 name:NSWorkspaceDidLaunchApplicationNotification
	 object:nil];
	
	path = [[[NSBundle mainBundle] pathForResource:@"Injected" ofType:@"plugsuit-support"] copy];
	//pathC = [path fileSystemRepresentation];
	
	managers = [[NSArray arrayWithObjects:
				 [[[PlSSIMBLManager alloc] init] autorelease],
				 [[[PlSInputManagerManager alloc] init] autorelease],
				 nil] retain];
	
	for (PlSManager* mgr in managers)
		mgr.preferencesController = prefs;
	
	[self checkForUpdates];
}

- (NSArray*) managers {
	return managers;
}

- (BOOL) isAppDisabled:(NSBundle*) app {
	return [app isEqual:@"com.apple.systempreferences"] ||
	[app isEqual:@"com.apple.Xcode"] ||
	[prefs isApplicationExcluded:app];
}

- (void) _appLaunched:(NSNotification*) notif {
	NSDictionary* info = [notif userInfo];
	NSNumber* pidObject = [info objectForKey:@"NSApplicationProcessIdentifier"];
	if (!pidObject || ![pidObject isKindOfClass:[NSNumber class]])
		return;
	
	pid_t pid = [pidObject longValue];
	if (!PlSIsCocoaApplication(pid))
		return;
	
	NSString* bundlePath = [info objectForKey:@"NSApplicationPath"];
	if (!bundlePath || ![bundlePath isKindOfClass:[NSString class]])
		return;
	
	NSBundle* bundle = [NSBundle bundleWithPath:bundlePath];
	if (!bundle)
		return;
	if ([self isAppDisabled:bundle]) {
		L0Log(@"not injecting into app = %@ -- excluded by user.", bundle);
		return;
	}
	
	BOOL proceed = NO;
	[managers makeObjectsPerformSelector:@selector(rescan)];
	for (PlSManager* manager in managers) {
		for (PlSPlugIn* plugIn in manager.plugIns) {
			BOOL compatible = NO;
			for (PlSAppSpecifier* spec in plugIn.appSpecifiers) {
				if ([spec isCompatibleWithApplication:bundle]) {
					proceed = YES;
					compatible = YES;
					break;
				}
			}
			
			if (compatible && plugIn.canCheckQuarantineOnUserVisiblePath
				&& [plugIn.userVisiblePath hasPrefix:NSHomeDirectory()]) {
				BOOL clearQuarantine = NO;
				
				NSURL* url = [NSURL fileURLWithPath:plugIn.userVisiblePath];
				FSRef ref;
				
				CFURLGetFSRef((CFURLRef) url, &ref);
				NSDictionary* attrs;
				if (LSCopyItemAttribute(&ref, kLSRolesAll, kLSItemQuarantineProperties, (CFTypeRef*) &attrs) == noErr) {
					[attrs autorelease];
					if ([[attrs objectForKey:(id) kLSQuarantineTypeKey] isEqual:(id) kLSQuarantineTypeWebDownload]) {
						NSString* agent = [attrs objectForKey:(id) kLSQuarantineAgentNameKey];
						NSURL* url = [attrs objectForKey:(id) kLSQuarantineOriginURLKey];
						NSDate* timestamp = [attrs objectForKey:(id) kLSQuarantineTimeStampKey];
						
						NSDateFormatter* date = [[NSDateFormatter alloc] init];
						[date setFormatterBehavior:NSDateFormatterBehavior10_4];
						[date setDateStyle:NSDateFormatterFullStyle];
						[date setTimeStyle:NSDateFormatterShortStyle];
						NSLocale* l = [[NSLocale alloc] initWithLocaleIdentifier:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]];
						[date setLocale:l];
						[l release];						
						NSString* dateString = [date stringFromDate:timestamp];
						[date release];
						
						NSAlert* a = [NSAlert alertNamed:@"PlSWebQuarantineAtAssessment"];
						[a setAlertStyle:NSCriticalAlertStyle];
						[a setMessageText:
						 [NSString stringWithFormat:[a messageText], [plugIn.userVisiblePath lastPathComponent]]];
						[a setInformativeText:
						 [NSString stringWithFormat:[a informativeText], agent, [url host], dateString]];
						[[a window] setLevel:NSFloatingWindowLevel];
						[NSApp activateIgnoringOtherApps:YES];
						int result = [a runModal];
						
						switch (result) {
							NSAlertFirstButtonReturn:
								clearQuarantine = YES; // enable
							NSAlertThirdButtonReturn: // open web page
								[[NSWorkspace sharedWorkspace] openURL:url];
								break;
						}
						
					} else {
						NSString* agent = [attrs objectForKey:(id) kLSQuarantineAgentNameKey];
						NSDate* timestamp = [attrs objectForKey:(id) kLSQuarantineTimeStampKey];
						
						NSDateFormatter* date = [[NSDateFormatter alloc] init];
						[date setFormatterBehavior:NSDateFormatterBehavior10_4];
						[date setDateStyle:NSDateFormatterFullStyle];
						[date setTimeStyle:NSDateFormatterShortStyle];
						NSLocale* l = [[NSLocale alloc] initWithLocaleIdentifier:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]];
						[date setLocale:l];
						[l release];
						NSString* dateString = [date stringFromDate:timestamp];
						[date release];
						
						NSAlert* a = [NSAlert alertNamed:@"PlSGenericQuarantineAtAssessment"];
						[a setAlertStyle:NSCriticalAlertStyle];
						[a setMessageText:
						 [NSString stringWithFormat:[a messageText], [plugIn.userVisiblePath lastPathComponent]]];
						[a setInformativeText:
						 [NSString stringWithFormat:[a informativeText], agent, dateString]];
						[[a window] setLevel:NSFloatingWindowLevel];
						[NSApp activateIgnoringOtherApps:YES];

						if ([a runModal] == NSAlertFirstButtonReturn)
							clearQuarantine = YES;
					}
					
					// clear quarantine
					if (clearQuarantine)
						LSSetItemAttribute(&ref, kLSRolesAll, kLSItemQuarantineProperties, NULL);
				}
			}
		}
	}
	
	if (!proceed)
		return;
	
	NSLog(@"%@", path); // REMOVEME
	mach_error_t err = mach_inject_bundle_pid([path fileSystemRepresentation], pid);
	
	L0Log(@"Injection happened for app = %@, pid = %d, with result %d", info, pid, err);
}

- (PlSUpdateUI*) updateUI { return _updateUI; }
- (PlSCFPreferences*) preferences { return prefs; }

@end
